﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Media;
using System.Diagnostics;

namespace ThapHaNoi
{
    public partial class frmMain : Form
    {
        int soDia; // số lượng đĩa
        Label[] dia; // mảng các label biểu diễn cho các đĩa
        int[] viTri; // mảng lưu vị trí của đĩa(viTri[i]=1 : vị trí của đĩa thứ i là cột một)
        int currStep ; // bước hiện tại
        SoundPlayer sound = new SoundPlayer("sound1.wav");

        public frmMain()
        {
            InitializeComponent();
        }

        private void frmMain_Paint(object sendr, PaintEventArgs e)
        {
            Graphics g = e.Graphics;
            Pen pen = new Pen(Color.White);
            Point p1;
            Point p2;

            // vẽ cột 1
            p1 = new Point(150, 200);
            p2 = new Point(150, 400);
            g.DrawLine(pen, p1, p2);

            // vẽ cột 2
            p1 = new Point(450, 200);
            p2 = new Point(450, 400);
            g.DrawLine(pen, p1, p2);

            // vẽ cột 3
            p1 = new Point(750, 200);
            p2 = new Point(750, 400);
            g.DrawLine(pen, p1, p2);
        }

        private void cboSoDia_SelectedIndexChanged(object sender, EventArgs e)
        {
            Random rand = new Random();
            this.Controls.Clear(); 

            // thêm các control của chương trình
            this.Controls.Add(lblTenChuongTrinh);
            this.Controls.Add(lblChonDia);
            this.Controls.Add(lblChonThuatToan);
            this.Controls.Add(lblBuocToiThieu);
            this.Controls.Add(lblBuocHienTai);
            this.Controls.Add(lblCot1);
            this.Controls.Add(lblCot2);
            this.Controls.Add(lblCot3);
            this.Controls.Add(cboSoDia);
            this.Controls.Add(cboThuatToan);
            this.Controls.Add(btnChuyenDia);
            this.Controls.Add(btnReset);

            btnReset.Enabled = false;
            soDia = int.Parse(cboSoDia.SelectedItem.ToString());
            lblBuocToiThieu.Text = "Số bước tối thiểu : " + (Math.Pow(2, soDia) - 1).ToString();
            lblBuocHienTai.Text = "Bước hiện tại :        0";
            viTri = new int[soDia + 1];
            currStep = 0;

            // tạo các label biểu diễn các đĩa
            dia = new Label[soDia + 1];
            for (int i = 1; i <= soDia; i++)
            {
                dia[i] = new Label();
                viTri[i] = 1; // khởi tạo vị trí ban đầu của các đĩa đều ở cột 1
                dia[i].Height = 20;
                dia[i].BackColor = Color.FromArgb(rand.Next(255), rand.Next(255), rand.Next(255));
                if (i == 1)
                {
                    dia[i].Width = 100;
                    dia[i].Left = 100;
                    dia[i].Top = 400 - 20 * soDia;
                }
                else
                {
                    dia[i].Width = dia[i - 1].Width + 40;
                    dia[i].Left = dia[i - 1].Left - 20;
                    dia[i].Top = dia[i - 1].Top + 20;
                }
                this.Controls.Add(dia[i]);
            }
        }

        /*
         * hàm biểu diễn sự di chuyển của một đĩa từ cột nguồn sang cột đích
        */
        private void DiaDiChuyen(int cotNguon, int cotDich)
        {
            lblBuocHienTai.Text = "Bước hiện tại :        "+currStep.ToString();
            Label lDia = dia[1];

            // tìm đĩa cần di chuyển
            for (int i = 1; i <= soDia; i++)
            {
                if (viTri[i] == cotNguon)
                {
                    lDia = dia[i];
                    viTri[i] = cotDich; // chuyển vị trí của đĩa cần di chuyển sang cột đích
                    break;
                }
            }

            // tìm số đĩa đã có trong cột đích
            int dichCount = -1;
            for (int i = 1; i <= soDia; i++)
            {
                if (viTri[i] == cotDich)
                {
                    dichCount++;
                }
            }

            int vanTocDia = 1; // vận tốc di chuyển của đĩa

            // đưa đĩa lên cao (khi đĩa đang ở cột nguồn)
            while (lDia.Top > 120)
            {
                lDia.Top -= vanTocDia;
                Application.DoEvents();
                Thread.Sleep(3);
            }

            // xác định vị trí cột đích
            int viTriMoi;
            if (cotDich == 1)
            {
                viTriMoi = 150;
            }
            else if (cotDich == 2)
            {
                viTriMoi = 450;
            }
            else
            {
                viTriMoi = 750;
            }

            // di chuyển đĩa sang phải/trái
            if (lDia.Left + lDia.Width / 2 < viTriMoi)
            {
                while (lDia.Left + lDia.Width / 2 < viTriMoi)
                {
                    lDia.Left += vanTocDia;
                    Application.DoEvents();
                    Thread.Sleep(3);
                }
            }
            else if (lDia.Left + lDia.Width / 2 > viTriMoi)
            {
                while (lDia.Left + lDia.Width / 2 > viTriMoi)
                {
                    lDia.Left -= vanTocDia;
                    Application.DoEvents();
                    Thread.Sleep(3);
                }
            }

            // hạ đĩa xuống thấp (khi đĩa đã di chuyển đến vị trí của cột đích)
            while (lDia.Top < 380 - dichCount * 20)
            {
                lDia.Top += vanTocDia;
                Application.DoEvents();
                Thread.Sleep(3);
            }
        }

        /*
         * hàm di chuyển các đĩa theo thuật đệ quy
        */
        private void THNDeQuy(int soDia, int cotNguon, int cotDich, int cotTrungGian)
        {
            if (soDia > 0)
            {
                THNDeQuy(soDia - 1, cotNguon, cotTrungGian, cotDich);
                currStep++;
                DiaDiChuyen(cotNguon, cotDich);
                sound.Play();
                THNDeQuy(soDia - 1, cotTrungGian, cotDich, cotNguon);
            }
        }

        /// <summary>
        /// stuct trạng thái của mức đệ quy đang thực hiện 
        /// được sử dụng trong giải thuật khử đệ quy
        /// </summary>
        public struct TrangThai
        {
            public int maxID; // chỉ số của đĩa lớn nhất trong mức
            public int num; // số đĩa của mức
            public int cNguon;
            public int cTrungGian;
            public int cDich;
            public TrangThai(int id, int n, int cn, int ct, int cd)
            {
                maxID = id;
                num = n;
                cNguon = cn;
                cTrungGian = ct;
                cDich = cd;
            }
        }

        /*
         * hàm di chuyển các đĩa theo giải thuật khử đệ quy sử dụng stack
        */
        private void THNKhongDeQuy(int soDia, int cotNguon, int cotDich, int cotTrungGian)
        {
            Stack<TrangThai> st = new Stack<TrangThai>();
            TrangThai tt = new TrangThai(soDia, soDia, cotNguon, cotTrungGian, cotDich);
            st.Push(tt);
            while (st.Count != 0)
            {
                TrangThai tt0 = st.Pop();
                if (tt0.num == 1)
                {
                    currStep++;
                    DiaDiChuyen(tt0.cNguon, tt0.cDich);
                    sound.Play();
                }
                else
                {
                    TrangThai tt1 = new TrangThai(tt0.maxID - 1, tt0.maxID - 1, tt0.cTrungGian, tt0.cNguon, tt0.cDich);
                    st.Push(tt1);
                    TrangThai tt2 = new TrangThai(tt0.maxID, 1, tt0.cNguon, tt0.cTrungGian, tt0.cDich);
                    st.Push(tt2);
                    TrangThai tt3 = new TrangThai(tt0.maxID - 1, tt0.maxID - 1, tt0.cNguon, tt0.cDich, tt0.cTrungGian);
                    st.Push(tt3);
                }
            }
        }
                 
        private void btnChuyenDia_Click(object sender, EventArgs e)
        {
            if (soDia < 1)
            {
                MessageBox.Show("bạn phải chọn số đĩa");
                return;
            }
            if (cboThuatToan.Text == "")
            {
                MessageBox.Show("bạn phải chọn thuật toán");
                return;
            }
            if (btnReset.Enabled)
            {
                MessageBox.Show("bạn phải reset hoặc chọn lại số đĩa");
                return;
            }

            // không cho phép các control làm ảnh hưởng đến quá trình chuyển các đĩa
            cboThuatToan.Enabled = false;
            cboSoDia.Enabled = false;
            btnChuyenDia.Enabled = false;

            // thực hiện quá trình chuyển đĩa
            if (cboThuatToan.SelectedItem.ToString() == "Đệ quy")
                THNDeQuy(soDia, 1, 3, 2);
            else
                THNKhongDeQuy(soDia, 1, 3, 2);
 
            MessageBox.Show("việc chuyển đĩa đã hoàn tất!");

            // cho phép các control có thể hoạt động trở lại sau khi quá trình chuyển các đĩa hoàn tất
            cboSoDia.Enabled = true;
            cboThuatToan.Enabled = true;
            btnChuyenDia.Enabled = true;
            btnReset.Enabled = true;
        }

        private void btnReset_Click(object sender, EventArgs e)
        {
            cboSoDia_SelectedIndexChanged(sender, e);
        }

        private void frmMain_Load(object sender, EventArgs e)
        {
            cboSoDia.Items.Clear();

            // nạp các giá trị cho cboSoDia
            for (int i = 1; i <= 5; i++)
            {
                cboSoDia.Items.Add(i);
            }

            cboThuatToan.Items.Clear();

            // nạp các lựa chọn cho cboThuatToan
            cboThuatToan.Items.Add("Đệ quy");
            cboThuatToan.Items.Add("Không đệ quy");
        }

        private void frmMain_FormClosed(object sender, FormClosedEventArgs e)
        {
            Process.GetCurrentProcess().Kill();
        }
    }
}
